//===- MipsRegisterBankInfo.cpp ---------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
/// \file
/// This file implements the targeting of the RegisterBankInfo class for Mips.
/// \todo This should be generated by TableGen.
//===----------------------------------------------------------------------===//

#include "MipsRegisterBankInfo.h"
#include "MipsInstrInfo.h"
#include "MipsTargetMachine.h"
#include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h"
#include "llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h"
#include "llvm/CodeGen/GlobalISel/LegalizerHelper.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"

#define GET_TARGET_REGBANK_IMPL

#include "MipsGenRegisterBank.inc"

namespace llvm {
namespace Mips {
enum PartialMappingIdx {
  PMI_GPR,
  PMI_SPR,
  PMI_DPR,
  PMI_MSA,
  PMI_Min = PMI_GPR,
};

RegisterBankInfo::PartialMapping PartMappings[]{
    {0, 32, GPRBRegBank},
    {0, 32, FPRBRegBank},
    {0, 64, FPRBRegBank},
    {0, 128, FPRBRegBank}
};

enum ValueMappingIdx {
    InvalidIdx = 0,
    GPRIdx = 1,
    SPRIdx = 4,
    DPRIdx = 7,
    MSAIdx = 10
};

RegisterBankInfo::ValueMapping ValueMappings[] = {
    // invalid
    {nullptr, 0},
    // up to 3 operands in GPRs
    {&PartMappings[PMI_GPR - PMI_Min], 1},
    {&PartMappings[PMI_GPR - PMI_Min], 1},
    {&PartMappings[PMI_GPR - PMI_Min], 1},
    // up to 3 operands in FPRs - single precission
    {&PartMappings[PMI_SPR - PMI_Min], 1},
    {&PartMappings[PMI_SPR - PMI_Min], 1},
    {&PartMappings[PMI_SPR - PMI_Min], 1},
    // up to 3 operands in FPRs - double precission
    {&PartMappings[PMI_DPR - PMI_Min], 1},
    {&PartMappings[PMI_DPR - PMI_Min], 1},
    {&PartMappings[PMI_DPR - PMI_Min], 1},
    // up to 3 operands in FPRs - MSA
    {&PartMappings[PMI_MSA - PMI_Min], 1},
    {&PartMappings[PMI_MSA - PMI_Min], 1},
    {&PartMappings[PMI_MSA - PMI_Min], 1}
};

} // end namespace Mips
} // end namespace llvm

using namespace llvm;

MipsRegisterBankInfo::MipsRegisterBankInfo(const TargetRegisterInfo &TRI)
    : MipsGenRegisterBankInfo() {}

const RegisterBank &
MipsRegisterBankInfo::getRegBankFromRegClass(const TargetRegisterClass &RC,
                                             LLT) const {
  using namespace Mips;

  switch (RC.getID()) {
  case Mips::GPR32RegClassID:
  case Mips::CPU16Regs_and_GPRMM16ZeroRegClassID:
  case Mips::GPRMM16MovePPairFirstRegClassID:
  case Mips::CPU16Regs_and_GPRMM16MovePPairSecondRegClassID:
  case Mips::GPRMM16MoveP_and_CPU16Regs_and_GPRMM16ZeroRegClassID:
  case Mips::GPRMM16MovePPairFirst_and_GPRMM16MovePPairSecondRegClassID:
  case Mips::SP32RegClassID:
  case Mips::GP32RegClassID:
    return getRegBank(Mips::GPRBRegBankID);
  case Mips::FGRCCRegClassID:
  case Mips::FGR32RegClassID:
  case Mips::FGR64RegClassID:
  case Mips::AFGR64RegClassID:
  case Mips::MSA128BRegClassID:
  case Mips::MSA128HRegClassID:
  case Mips::MSA128WRegClassID:
  case Mips::MSA128DRegClassID:
    return getRegBank(Mips::FPRBRegBankID);
  default:
    llvm_unreachable("Register class not supported");
  }
}

// Instructions where all register operands are floating point.
static bool isFloatingPointOpcode(unsigned Opc) {
  switch (Opc) {
  case TargetOpcode::G_FCONSTANT:
  case TargetOpcode::G_FADD:
  case TargetOpcode::G_FSUB:
  case TargetOpcode::G_FMUL:
  case TargetOpcode::G_FDIV:
  case TargetOpcode::G_FABS:
  case TargetOpcode::G_FSQRT:
  case TargetOpcode::G_FCEIL:
  case TargetOpcode::G_FFLOOR:
  case TargetOpcode::G_FPEXT:
  case TargetOpcode::G_FPTRUNC:
    return true;
  default:
    return false;
  }
}

// Instructions where use operands are floating point registers.
// Def operands are general purpose.
static bool isFloatingPointOpcodeUse(unsigned Opc) {
  switch (Opc) {
  case TargetOpcode::G_FPTOSI:
  case TargetOpcode::G_FPTOUI:
  case TargetOpcode::G_FCMP:
    return true;
  default:
    return isFloatingPointOpcode(Opc);
  }
}

// Instructions where def operands are floating point registers.
// Use operands are general purpose.
static bool isFloatingPointOpcodeDef(unsigned Opc) {
  switch (Opc) {
  case TargetOpcode::G_SITOFP:
  case TargetOpcode::G_UITOFP:
    return true;
  default:
    return isFloatingPointOpcode(Opc);
  }
}

static bool isGprbTwoInstrUnalignedLoadOrStore(const MachineInstr *MI) {
  if (MI->getOpcode() == TargetOpcode::G_LOAD ||
      MI->getOpcode() == TargetOpcode::G_STORE) {
    auto MMO = *MI->memoperands_begin();
    const MipsSubtarget &STI =
        static_cast<const MipsSubtarget &>(MI->getMF()->getSubtarget());
    if (MMO->getSize() == 4 && (!STI.systemSupportsUnalignedAccess() &&
                                MMO->getAlign() < MMO->getSize()))
      return true;
  }
  return false;
}

static bool isAmbiguous(unsigned Opc) {
  switch (Opc) {
  case TargetOpcode::G_LOAD:
  case TargetOpcode::G_STORE:
  case TargetOpcode::G_PHI:
  case TargetOpcode::G_SELECT:
  case TargetOpcode::G_IMPLICIT_DEF:
  case TargetOpcode::G_UNMERGE_VALUES:
  case TargetOpcode::G_MERGE_VALUES:
    return true;
  default:
    return false;
  }
}

void MipsRegisterBankInfo::AmbiguousRegDefUseContainer::addDefUses(
    Register Reg, const MachineRegisterInfo &MRI) {
  assert(!MRI.getType(Reg).isPointer() &&
         "Pointers are gprb, they should not be considered as ambiguous.\n");
  for (MachineInstr &UseMI : MRI.use_instructions(Reg)) {
    MachineInstr *NonCopyInstr = skipCopiesOutgoing(&UseMI);
    // Copy with many uses.
    if (NonCopyInstr->getOpcode() == TargetOpcode::COPY &&
        !Register::isPhysicalRegister(NonCopyInstr->getOperand(0).getReg()))
      addDefUses(NonCopyInstr->getOperand(0).getReg(), MRI);
    else
      DefUses.push_back(skipCopiesOutgoing(&UseMI));
  }
}

void MipsRegisterBankInfo::AmbiguousRegDefUseContainer::addUseDef(
    Register Reg, const MachineRegisterInfo &MRI) {
  assert(!MRI.getType(Reg).isPointer() &&
         "Pointers are gprb, they should not be considered as ambiguous.\n");
  MachineInstr *DefMI = MRI.getVRegDef(Reg);
  UseDefs.push_back(skipCopiesIncoming(DefMI));
}

MachineInstr *
MipsRegisterBankInfo::AmbiguousRegDefUseContainer::skipCopiesOutgoing(
    MachineInstr *MI) const {
  const MachineFunction &MF = *MI->getParent()->getParent();
  const MachineRegisterInfo &MRI = MF.getRegInfo();
  MachineInstr *Ret = MI;
  while (Ret->getOpcode() == TargetOpcode::COPY &&
         !Register::isPhysicalRegister(Ret->getOperand(0).getReg()) &&
         MRI.hasOneUse(Ret->getOperand(0).getReg())) {
    Ret = &(*MRI.use_instr_begin(Ret->getOperand(0).getReg()));
  }
  return Ret;
}

MachineInstr *
MipsRegisterBankInfo::AmbiguousRegDefUseContainer::skipCopiesIncoming(
    MachineInstr *MI) const {
  const MachineFunction &MF = *MI->getParent()->getParent();
  const MachineRegisterInfo &MRI = MF.getRegInfo();
  MachineInstr *Ret = MI;
  while (Ret->getOpcode() == TargetOpcode::COPY &&
         !Register::isPhysicalRegister(Ret->getOperand(1).getReg()))
    Ret = MRI.getVRegDef(Ret->getOperand(1).getReg());
  return Ret;
}

MipsRegisterBankInfo::AmbiguousRegDefUseContainer::AmbiguousRegDefUseContainer(
    const MachineInstr *MI) {
  assert(isAmbiguous(MI->getOpcode()) &&
         "Not implemented for non Ambiguous opcode.\n");

  const MachineRegisterInfo &MRI = MI->getMF()->getRegInfo();

  if (MI->getOpcode() == TargetOpcode::G_LOAD)
    addDefUses(MI->getOperand(0).getReg(), MRI);

  if (MI->getOpcode() == TargetOpcode::G_STORE)
    addUseDef(MI->getOperand(0).getReg(), MRI);

  if (MI->getOpcode() == TargetOpcode::G_PHI) {
    addDefUses(MI->getOperand(0).getReg(), MRI);

    for (unsigned i = 1; i < MI->getNumOperands(); i += 2)
      addUseDef(MI->getOperand(i).getReg(), MRI);
  }

  if (MI->getOpcode() == TargetOpcode::G_SELECT) {
    addDefUses(MI->getOperand(0).getReg(), MRI);

    addUseDef(MI->getOperand(2).getReg(), MRI);
    addUseDef(MI->getOperand(3).getReg(), MRI);
  }

  if (MI->getOpcode() == TargetOpcode::G_IMPLICIT_DEF)
    addDefUses(MI->getOperand(0).getReg(), MRI);

  if (MI->getOpcode() == TargetOpcode::G_UNMERGE_VALUES)
    addUseDef(MI->getOperand(MI->getNumOperands() - 1).getReg(), MRI);

  if (MI->getOpcode() == TargetOpcode::G_MERGE_VALUES)
      addDefUses(MI->getOperand(0).getReg(), MRI);
}

bool MipsRegisterBankInfo::TypeInfoForMF::visit(
    const MachineInstr *MI, const MachineInstr *WaitingForTypeOfMI,
    InstType &AmbiguousTy) {
  assert(isAmbiguous(MI->getOpcode()) && "Visiting non-Ambiguous opcode.\n");
  if (wasVisited(MI))
    return true; // InstType has already been determined for MI.

  startVisit(MI);
  AmbiguousRegDefUseContainer DefUseContainer(MI);

  if (isGprbTwoInstrUnalignedLoadOrStore(MI)) {
    setTypes(MI, Integer);
    return true;
  }

  if (AmbiguousTy == InstType::Ambiguous &&
      (MI->getOpcode() == TargetOpcode::G_MERGE_VALUES ||
       MI->getOpcode() == TargetOpcode::G_UNMERGE_VALUES))
    AmbiguousTy = InstType::AmbiguousWithMergeOrUnmerge;

  // Visit instructions where MI's DEF operands are USED.
  if (visitAdjacentInstrs(MI, DefUseContainer.getDefUses(), true, AmbiguousTy))
    return true;

  // Visit instructions that DEFINE MI's USE operands.
  if (visitAdjacentInstrs(MI, DefUseContainer.getUseDefs(), false, AmbiguousTy))
    return true;

  // All MI's adjacent instructions, are ambiguous.
  if (!WaitingForTypeOfMI) {
    // This is chain of ambiguous instructions.
    setTypes(MI, AmbiguousTy);
    return true;
  }
  // Excluding WaitingForTypeOfMI, MI is either connected to chains of ambiguous
  // instructions or has no other adjacent instructions. Anyway InstType could
  // not be determined. There could be unexplored path from some of
  // WaitingForTypeOfMI's adjacent instructions to an instruction with only one
  // mapping available.
  // We are done with this branch, add MI to WaitingForTypeOfMI's WaitingQueue,
  // this way when WaitingForTypeOfMI figures out its InstType same InstType
  // will be assigned to all instructions in this branch.
  addToWaitingQueue(WaitingForTypeOfMI, MI);
  return false;
}

bool MipsRegisterBankInfo::TypeInfoForMF::visitAdjacentInstrs(
    const MachineInstr *MI, SmallVectorImpl<MachineInstr *> &AdjacentInstrs,
    bool isDefUse, InstType &AmbiguousTy) {
  while (!AdjacentInstrs.empty()) {
    MachineInstr *AdjMI = AdjacentInstrs.pop_back_val();

    if (isDefUse ? isFloatingPointOpcodeUse(AdjMI->getOpcode())
                 : isFloatingPointOpcodeDef(AdjMI->getOpcode())) {
      setTypes(MI, InstType::FloatingPoint);
      return true;
    }

    // Determine InstType from register bank of phys register that is
    // 'isDefUse ? def : use' of this copy.
    if (AdjMI->getOpcode() == TargetOpcode::COPY) {
      setTypesAccordingToPhysicalRegister(MI, AdjMI, isDefUse ? 0 : 1);
      return true;
    }

    // Defaults to integer instruction. Small registers in G_MERGE (uses) and
    // G_UNMERGE (defs) will always be gprb.
    if ((!isDefUse && AdjMI->getOpcode() == TargetOpcode::G_UNMERGE_VALUES) ||
        (isDefUse && AdjMI->getOpcode() == TargetOpcode::G_MERGE_VALUES) ||
        !isAmbiguous(AdjMI->getOpcode())) {
      setTypes(MI, InstType::Integer);
      return true;
    }

    // When AdjMI was visited first, MI has to continue to explore remaining
    // adjacent instructions and determine InstType without visiting AdjMI.
    if (!wasVisited(AdjMI) ||
        getRecordedTypeForInstr(AdjMI) != InstType::NotDetermined) {
      if (visit(AdjMI, MI, AmbiguousTy)) {
        // InstType is successfully determined and is same as for AdjMI.
        setTypes(MI, getRecordedTypeForInstr(AdjMI));
        return true;
      }
    }
  }
  return false;
}

void MipsRegisterBankInfo::TypeInfoForMF::setTypes(const MachineInstr *MI,
                                                   InstType InstTy) {
  changeRecordedTypeForInstr(MI, InstTy);
  for (const MachineInstr *WaitingInstr : getWaitingQueueFor(MI)) {
    setTypes(WaitingInstr, InstTy);
  }
}

void MipsRegisterBankInfo::TypeInfoForMF::setTypesAccordingToPhysicalRegister(
    const MachineInstr *MI, const MachineInstr *CopyInst, unsigned Op) {
  assert((Register::isPhysicalRegister(CopyInst->getOperand(Op).getReg())) &&
         "Copies of non physical registers should not be considered here.\n");

  const MachineFunction &MF = *CopyInst->getMF();
  const MachineRegisterInfo &MRI = MF.getRegInfo();
  const TargetRegisterInfo &TRI = *MF.getSubtarget().getRegisterInfo();
  const RegisterBankInfo &RBI =
      *CopyInst->getMF()->getSubtarget().getRegBankInfo();
  const RegisterBank *Bank =
      RBI.getRegBank(CopyInst->getOperand(Op).getReg(), MRI, TRI);

  if (Bank == &Mips::FPRBRegBank)
    setTypes(MI, InstType::FloatingPoint);
  else if (Bank == &Mips::GPRBRegBank)
    setTypes(MI, InstType::Integer);
  else
    llvm_unreachable("Unsupported register bank.\n");
}

MipsRegisterBankInfo::InstType
MipsRegisterBankInfo::TypeInfoForMF::determineInstType(const MachineInstr *MI) {
  InstType DefaultAmbiguousType = InstType::Ambiguous;
  visit(MI, nullptr, DefaultAmbiguousType);
  return getRecordedTypeForInstr(MI);
}

void MipsRegisterBankInfo::TypeInfoForMF::cleanupIfNewFunction(
    llvm::StringRef FunctionName) {
  if (MFName != FunctionName) {
    MFName = std::string(FunctionName);
    WaitingQueues.clear();
    Types.clear();
  }
}

static const MipsRegisterBankInfo::ValueMapping *
getMSAMapping(const MachineFunction &MF) {
  assert(static_cast<const MipsSubtarget &>(MF.getSubtarget()).hasMSA() &&
         "MSA mapping not available on target without MSA.");
  return &Mips::ValueMappings[Mips::MSAIdx];
}

static const MipsRegisterBankInfo::ValueMapping *getFprbMapping(unsigned Size) {
  return Size == 32 ? &Mips::ValueMappings[Mips::SPRIdx]
                    : &Mips::ValueMappings[Mips::DPRIdx];
}

static const unsigned CustomMappingID = 1;

// Only 64 bit mapping is available in fprb and will be marked as custom, i.e.
// will be split into two 32 bit registers in gprb.
static const MipsRegisterBankInfo::ValueMapping *
getGprbOrCustomMapping(unsigned Size, unsigned &MappingID) {
  if (Size == 32)
    return &Mips::ValueMappings[Mips::GPRIdx];

  MappingID = CustomMappingID;
  return &Mips::ValueMappings[Mips::DPRIdx];
}

const RegisterBankInfo::InstructionMapping &
MipsRegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {

  static TypeInfoForMF TI;

  // Reset TI internal data when MF changes.
  TI.cleanupIfNewFunction(MI.getMF()->getName());

  unsigned Opc = MI.getOpcode();
  const MachineFunction &MF = *MI.getParent()->getParent();
  const MachineRegisterInfo &MRI = MF.getRegInfo();

  if (MI.getOpcode() != TargetOpcode::G_PHI) {
    const RegisterBankInfo::InstructionMapping &Mapping =
        getInstrMappingImpl(MI);
    if (Mapping.isValid())
      return Mapping;
  }

  using namespace TargetOpcode;

  unsigned NumOperands = MI.getNumOperands();
  const ValueMapping *OperandsMapping = &Mips::ValueMappings[Mips::GPRIdx];
  unsigned MappingID = DefaultMappingID;

  // Check if LLT sizes match sizes of available register banks.
  for (const MachineOperand &Op : MI.operands()) {
    if (Op.isReg()) {
      LLT RegTy = MRI.getType(Op.getReg());

      if (RegTy.isScalar() &&
          (RegTy.getSizeInBits() != 32 && RegTy.getSizeInBits() != 64))
        return getInvalidInstructionMapping();

      if (RegTy.isVector() && RegTy.getSizeInBits() != 128)
        return getInvalidInstructionMapping();
    }
  }

  const LLT Op0Ty = MRI.getType(MI.getOperand(0).getReg());
  unsigned Op0Size = Op0Ty.getSizeInBits();
  InstType InstTy = InstType::Integer;

  switch (Opc) {
  case G_TRUNC:
  case G_UMULH:
  case G_ZEXTLOAD:
  case G_SEXTLOAD:
  case G_PTR_ADD:
  case G_INTTOPTR:
  case G_PTRTOINT:
  case G_AND:
  case G_OR:
  case G_XOR:
  case G_SHL:
  case G_ASHR:
  case G_LSHR:
  case G_BRINDIRECT:
  case G_VASTART:
  case G_BSWAP:
  case G_CTLZ:
    OperandsMapping = &Mips::ValueMappings[Mips::GPRIdx];
    break;
  case G_ADD:
  case G_SUB:
  case G_MUL:
  case G_SDIV:
  case G_SREM:
  case G_UDIV:
  case G_UREM:
    OperandsMapping = &Mips::ValueMappings[Mips::GPRIdx];
    if (Op0Size == 128)
      OperandsMapping = getMSAMapping(MF);
    break;
  case G_STORE:
  case G_LOAD: {
    if (Op0Size == 128) {
      OperandsMapping = getOperandsMapping(
          {getMSAMapping(MF), &Mips::ValueMappings[Mips::GPRIdx]});
      break;
    }

    if (!Op0Ty.isPointer())
      InstTy = TI.determineInstType(&MI);

    if (isFloatingPoint_32or64(InstTy, Op0Size) ||
        isAmbiguous_64(InstTy, Op0Size)) {
      OperandsMapping = getOperandsMapping(
          {getFprbMapping(Op0Size), &Mips::ValueMappings[Mips::GPRIdx]});
    } else {
      assert((isInteger_32(InstTy, Op0Size) ||
              isAmbiguous_32(InstTy, Op0Size) ||
              isAmbiguousWithMergeOrUnmerge_64(InstTy, Op0Size)) &&
             "Unexpected Inst type");
      OperandsMapping =
          getOperandsMapping({getGprbOrCustomMapping(Op0Size, MappingID),
                              &Mips::ValueMappings[Mips::GPRIdx]});
    }

    break;
  }
  case G_PHI: {
    if (!Op0Ty.isPointer())
      InstTy = TI.determineInstType(&MI);

    // PHI is copylike and should have one regbank in mapping for def register.
    if (isAmbiguousWithMergeOrUnmerge_64(InstTy, Op0Size)) {
      OperandsMapping =
          getOperandsMapping({&Mips::ValueMappings[Mips::DPRIdx]});
      TI.clearTypeInfoData(&MI);
      return getInstructionMapping(CustomMappingID, /*Cost=*/1, OperandsMapping,
                                   /*NumOperands=*/1);
    }
    assert((isInteger_32(InstTy, Op0Size) ||
            isFloatingPoint_32or64(InstTy, Op0Size) ||
            isAmbiguous_32or64(InstTy, Op0Size)) &&
           "Unexpected Inst type");
    // Use default handling for PHI, i.e. set reg bank of def operand to match
    // register banks of use operands.
    return getInstrMappingImpl(MI);
  }
  case G_SELECT: {
    if (!Op0Ty.isPointer())
      InstTy = TI.determineInstType(&MI);
    if (isFloatingPoint_32or64(InstTy, Op0Size) ||
        isAmbiguous_64(InstTy, Op0Size)) {
      const RegisterBankInfo::ValueMapping *Bank = getFprbMapping(Op0Size);
      OperandsMapping = getOperandsMapping(
          {Bank, &Mips::ValueMappings[Mips::GPRIdx], Bank, Bank});
      break;
    } else {
      assert((isInteger_32(InstTy, Op0Size) ||
              isAmbiguous_32(InstTy, Op0Size) ||
              isAmbiguousWithMergeOrUnmerge_64(InstTy, Op0Size)) &&
             "Unexpected Inst type");
      const RegisterBankInfo::ValueMapping *Bank =
          getGprbOrCustomMapping(Op0Size, MappingID);
      OperandsMapping = getOperandsMapping(
          {Bank, &Mips::ValueMappings[Mips::GPRIdx], Bank, Bank});
    }
    break;
  }
  case G_IMPLICIT_DEF: {
    if (!Op0Ty.isPointer())
      InstTy = TI.determineInstType(&MI);

    if (isFloatingPoint_32or64(InstTy, Op0Size))
      OperandsMapping = getFprbMapping(Op0Size);
    else {
      assert((isInteger_32(InstTy, Op0Size) ||
              isAmbiguousWithMergeOrUnmerge_64(InstTy, Op0Size)) &&
             "Unexpected Inst type");
      OperandsMapping = getGprbOrCustomMapping(Op0Size, MappingID);
    }
  } break;
  case G_UNMERGE_VALUES: {
    assert(MI.getNumOperands() == 3 && "Unsupported G_UNMERGE_VALUES");
    unsigned Op3Size = MRI.getType(MI.getOperand(2).getReg()).getSizeInBits();
    InstTy = TI.determineInstType(&MI);
    assert((isAmbiguousWithMergeOrUnmerge_64(InstTy, Op3Size) ||
            isFloatingPoint_64(InstTy, Op3Size)) &&
           "Unexpected Inst type");
    OperandsMapping = getOperandsMapping({&Mips::ValueMappings[Mips::GPRIdx],
                                          &Mips::ValueMappings[Mips::GPRIdx],
                                          &Mips::ValueMappings[Mips::DPRIdx]});
    if (isAmbiguousWithMergeOrUnmerge_64(InstTy, Op3Size))
      MappingID = CustomMappingID;
    break;
  }
  case G_MERGE_VALUES: {
    InstTy = TI.determineInstType(&MI);
    assert((isAmbiguousWithMergeOrUnmerge_64(InstTy, Op0Size) ||
            isFloatingPoint_64(InstTy, Op0Size)) &&
           "Unexpected Inst type");
    OperandsMapping = getOperandsMapping({&Mips::ValueMappings[Mips::DPRIdx],
                                          &Mips::ValueMappings[Mips::GPRIdx],
                                          &Mips::ValueMappings[Mips::GPRIdx]});
    if (isAmbiguousWithMergeOrUnmerge_64(InstTy, Op0Size))
      MappingID = CustomMappingID;
    break;
  }
  case G_FADD:
  case G_FSUB:
  case G_FMUL:
  case G_FDIV:
  case G_FABS:
  case G_FSQRT:
    OperandsMapping = getFprbMapping(Op0Size);
    if (Op0Size == 128)
      OperandsMapping = getMSAMapping(MF);
    break;
  case G_FCONSTANT:
    OperandsMapping = getOperandsMapping({getFprbMapping(Op0Size), nullptr});
    break;
  case G_FCMP: {
    unsigned Op2Size = MRI.getType(MI.getOperand(2).getReg()).getSizeInBits();
    OperandsMapping =
        getOperandsMapping({&Mips::ValueMappings[Mips::GPRIdx], nullptr,
                            getFprbMapping(Op2Size), getFprbMapping(Op2Size)});
    break;
  }
  case G_FPEXT:
    OperandsMapping = getOperandsMapping({&Mips::ValueMappings[Mips::DPRIdx],
                                          &Mips::ValueMappings[Mips::SPRIdx]});
    break;
  case G_FPTRUNC:
    OperandsMapping = getOperandsMapping({&Mips::ValueMappings[Mips::SPRIdx],
                                          &Mips::ValueMappings[Mips::DPRIdx]});
    break;
  case G_FPTOSI: {
    assert((Op0Size == 32) && "Unsupported integer size");
    unsigned SizeFP = MRI.getType(MI.getOperand(1).getReg()).getSizeInBits();
    OperandsMapping = getOperandsMapping(
        {&Mips::ValueMappings[Mips::GPRIdx], getFprbMapping(SizeFP)});
    break;
  }
  case G_SITOFP:
    assert((MRI.getType(MI.getOperand(1).getReg()).getSizeInBits() == 32) &&
           "Unsupported integer size");
    OperandsMapping = getOperandsMapping(
        {getFprbMapping(Op0Size), &Mips::ValueMappings[Mips::GPRIdx]});
    break;
  case G_CONSTANT:
  case G_FRAME_INDEX:
  case G_GLOBAL_VALUE:
  case G_JUMP_TABLE:
  case G_BRCOND:
    OperandsMapping =
        getOperandsMapping({&Mips::ValueMappings[Mips::GPRIdx], nullptr});
    break;
  case G_BRJT:
    OperandsMapping =
        getOperandsMapping({&Mips::ValueMappings[Mips::GPRIdx], nullptr,
                            &Mips::ValueMappings[Mips::GPRIdx]});
    break;
  case G_ICMP:
    OperandsMapping =
        getOperandsMapping({&Mips::ValueMappings[Mips::GPRIdx], nullptr,
                            &Mips::ValueMappings[Mips::GPRIdx],
                            &Mips::ValueMappings[Mips::GPRIdx]});
    break;
  default:
    return getInvalidInstructionMapping();
  }

  if (MappingID == CustomMappingID)
    TI.clearTypeInfoData(&MI);
  return getInstructionMapping(MappingID, /*Cost=*/1, OperandsMapping,
                               NumOperands);
}

using InstListTy = GISelWorkList<4>;
namespace {
class InstManager : public GISelChangeObserver {
  InstListTy &InstList;

public:
  InstManager(InstListTy &Insts) : InstList(Insts) {}

  void createdInstr(MachineInstr &MI) override { InstList.insert(&MI); }
  void erasingInstr(MachineInstr &MI) override {}
  void changingInstr(MachineInstr &MI) override {}
  void changedInstr(MachineInstr &MI) override {}
};
} // end anonymous namespace

void MipsRegisterBankInfo::setRegBank(MachineInstr &MI,
                                      MachineRegisterInfo &MRI) const {
  Register Dest = MI.getOperand(0).getReg();
  switch (MI.getOpcode()) {
  case TargetOpcode::G_STORE:
    // No def operands, skip this instruction.
    break;
  case TargetOpcode::G_CONSTANT:
  case TargetOpcode::G_LOAD:
  case TargetOpcode::G_SELECT:
  case TargetOpcode::G_PHI:
  case TargetOpcode::G_IMPLICIT_DEF: {
    assert(MRI.getType(Dest) == LLT::scalar(32) && "Unexpected operand type.");
    MRI.setRegBank(Dest, getRegBank(Mips::GPRBRegBankID));
    break;
  }
  case TargetOpcode::G_PTR_ADD: {
    assert(MRI.getType(Dest).isPointer() && "Unexpected operand type.");
    MRI.setRegBank(Dest, getRegBank(Mips::GPRBRegBankID));
    break;
  }
  default:
    llvm_unreachable("Unexpected opcode.");
  }
}

static void
combineAwayG_UNMERGE_VALUES(LegalizationArtifactCombiner &ArtCombiner,
                            GUnmerge &MI, GISelChangeObserver &Observer) {
  SmallVector<Register, 4> UpdatedDefs;
  SmallVector<MachineInstr *, 2> DeadInstrs;
  ArtCombiner.tryCombineUnmergeValues(MI, DeadInstrs,
                                      UpdatedDefs, Observer);
  for (MachineInstr *DeadMI : DeadInstrs)
    DeadMI->eraseFromParent();
}

void MipsRegisterBankInfo::applyMappingImpl(
    const OperandsMapper &OpdMapper) const {
  MachineInstr &MI = OpdMapper.getMI();
  InstListTy NewInstrs;
  MachineFunction *MF = MI.getMF();
  MachineRegisterInfo &MRI = OpdMapper.getMRI();
  const LegalizerInfo &LegInfo = *MF->getSubtarget().getLegalizerInfo();

  InstManager NewInstrObserver(NewInstrs);
  MachineIRBuilder B(MI, NewInstrObserver);
  LegalizerHelper Helper(*MF, NewInstrObserver, B);
  LegalizationArtifactCombiner ArtCombiner(B, MF->getRegInfo(), LegInfo);

  switch (MI.getOpcode()) {
  case TargetOpcode::G_LOAD:
  case TargetOpcode::G_STORE:
  case TargetOpcode::G_PHI:
  case TargetOpcode::G_SELECT:
  case TargetOpcode::G_IMPLICIT_DEF: {
    Helper.narrowScalar(MI, 0, LLT::scalar(32));
    // Handle new instructions.
    while (!NewInstrs.empty()) {
      MachineInstr *NewMI = NewInstrs.pop_back_val();
      // This is new G_UNMERGE that was created during narrowScalar and will
      // not be considered for regbank selection. RegBankSelect for mips
      // visits/makes corresponding G_MERGE first. Combine them here.
      if (auto *Unmerge = dyn_cast<GUnmerge>(NewMI))
        combineAwayG_UNMERGE_VALUES(ArtCombiner, *Unmerge, NewInstrObserver);
      // This G_MERGE will be combined away when its corresponding G_UNMERGE
      // gets regBankSelected.
      else if (NewMI->getOpcode() == TargetOpcode::G_MERGE_VALUES)
        continue;
      else
        // Manually set register banks for def operands to 32 bit gprb.
        setRegBank(*NewMI, MRI);
    }
    return;
  }
  case TargetOpcode::G_UNMERGE_VALUES:
    combineAwayG_UNMERGE_VALUES(ArtCombiner, cast<GUnmerge>(MI),
                                NewInstrObserver);
    return;
  default:
    break;
  }

  return applyDefaultMapping(OpdMapper);
}
